package com.sise.cn.config;

import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.alibaba.fastjson.support.config.FastJsonConfig;
import com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter;
import com.sise.cn.annotation.AccessAuthority;
import com.sise.cn.api.common.config.LoginConfig;
import com.sise.cn.cache.LoginAccountManager;
import com.sise.cn.constant.CommonConstant;
import com.sise.cn.enums.error.SystemError;
import com.sise.cn.utils.LoginAccountInfo;
import com.sise.cn.utils.SessionContextHolder;
import com.sise.cn.vo.HttpBody;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.web.servlet.MultipartConfigFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.format.Formatter;
import org.springframework.format.FormatterRegistry;
import org.springframework.http.MediaType;
import org.springframework.http.converter.ByteArrayHttpMessageConverter;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.StringHttpMessageConverter;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.web.servlet.mvc.ParameterizableViewController;
import org.springframework.web.servlet.resource.ResourceHttpRequestHandler;

import javax.servlet.MultipartConfigElement;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.nio.charset.Charset;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.*;

/**
 * @author CCJ
 */
@Slf4j
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {

    @Autowired(required = false)
    private Map<String, HandlerInterceptor> interceptor;

    @Autowired
    private LoginConfig loginConfig;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        if (CollectionUtils.isEmpty(interceptor)){
            return;
        }
        for (Map.Entry<String, HandlerInterceptor> entry : interceptor.entrySet()) {
            if (entry.getValue() != null) {
                registry.addInterceptor(entry.getValue());
            }
        }
    }

    @Override
    public void addFormatters(FormatterRegistry registry) {
        // 默认的DateFormatter不支持本格式，遂新增一个formatter
        registry.addFormatter(new Formatter<Date>() {
            private String format = "yyyy-MM-dd HH:mm:ss";

            @Override
            public Date parse(String text, Locale locale) throws ParseException {
                DateFormat dateFormat = new SimpleDateFormat(format, locale);
                return dateFormat.parse(text);
            }

            @Override
            public String print(Date object, Locale locale) {
                DateFormat dateFormat = new SimpleDateFormat(format, locale);
                return dateFormat.format(object);
            }
        });
    }

    @Override
    public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
        // 标注着@Restful 和 @RequestBody都会默认使用MappingJackson2HttpMessageConverter构造器
        // 这个构造器要求较多，不如fastjson好用，所以这里都改成使用fastjon来做解析
        converters.clear();

        // 1. 处理字符串, 避免直接返回字符串的时候被添加了引号
        StringHttpMessageConverter smc = new StringHttpMessageConverter(Charset.forName("UTF-8"));
        converters.add(smc);

        // 2. 处理JSON
        FastJsonHttpMessageConverter fastConverter = new FastJsonHttpMessageConverter();
        FastJsonConfig fastJsonConfig = new FastJsonConfig();
        fastJsonConfig.setSerializerFeatures(

                /*
                 * 结果格式化
                 */
                SerializerFeature.PrettyFormat,

                /*
                 * 输出key时使用双引号
                 */
                SerializerFeature.QuoteFieldNames,

                /*
                 * Date的日期转换器
                 */
                SerializerFeature.WriteDateUseDateFormat,

                /*
                 * 空字段处理
                 */
                SerializerFeature.WriteMapNullValue,

                SerializerFeature.WriteNullListAsEmpty,

                SerializerFeature.WriteNullStringAsEmpty
        );

        // 增加支持的media type
        List<MediaType> fastMediaTypes = new ArrayList<>(2);
        fastMediaTypes.add(MediaType.APPLICATION_JSON_UTF8);
        fastMediaTypes.add(MediaType.APPLICATION_JSON);

        fastConverter.setSupportedMediaTypes(fastMediaTypes);
//        fastConverter.setFastJsonConfig(fastJsonConfig);

        // fastjson转换器
        converters.add(fastConverter);


        // 3. 增加byte数组，处理下载文件功能
        ByteArrayHttpMessageConverter bac = new ByteArrayHttpMessageConverter();
        converters.add(bac);
    }

    /**
     * 文件上传配置
     *
     * @return
     */
    @Bean
    public MultipartConfigElement multipartConfigElement() {
        MultipartConfigFactory factory = new MultipartConfigFactory();
        //单个文件最大
        factory.setMaxFileSize((("100MB")));
        /*
        //设置总上传数据总大小
        factory.setMaxRequestSize("100MB");
        */
        return factory.createMultipartConfig();
    }

    //    @Bean
//    @ConditionalOnMissingBean(value = HandlerInterceptor.class)
    public HandlerInterceptor interceptor() {
        return new HandlerInterceptor() {
            @Override
            public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
                String token = getCookieToken(request, CommonConstant.COOKIE_TOKEN);
                log.info(request.getRequestURI() + "," + handler.getClass() + "," + request.getRemoteAddr() + "," + token);
                String uri = request.getRequestURI();
                boolean isAdmin = uri.contains("/admin/");
                String indexUrl;
                String loginUrl;
                if (isAdmin) {
                    indexUrl = loginConfig.getAdmin().getIndexUrl();
                    loginUrl = loginConfig.getAdmin().getLoginUrl();
                } else {
                    indexUrl = loginConfig.getCustomer().getIndexUrl();
                    loginUrl = loginConfig.getCustomer().getLoginUrl();
                }
                if (isPrivateLogin(uri)) {
                    //直接访问登陆页面，判断是否带有有效的token信息
                    boolean isTokenValid = validCookieToken(token);
                    if (isTokenValid) {
                        htmlWriteRedirect(response, loginUrl);
                        return false;
                    } else {
                        return true;
                    }
                }

                if (isParameterizableViewController(handler)) {
                    boolean isTokenValid = validCookieToken(token);
                    if (isTokenValid) {
                        htmlWriteRedirect(response, indexUrl);
                        return false;
                    } else {
                        htmlWriteRedirect(response, loginUrl);
                        return false;
                    }
                }
                //静态资源访问
                if (isResource(handler)) {
                    // 添加页面劫持头
                    response.addHeader("X-Frame-Options", "SAMEORIGIN");
                    return true;
                }
                //后端请求
                if (isHandlerMethod(handler)) {
                    HandlerMethod method = (HandlerMethod) handler;
                    if (isAccessAuthorityPublic(method)) {
                        return true;
                    }
                }
                boolean isTokenValid = validCookieToken(token);
                if (!isTokenValid) {
                    ajaxWriteRedirect(response, loginUrl);
                    return false;
                }
                return true;
            }
        };
    }

    private boolean isPrivateLogin(String uri) {
        if (StringUtils.isEmpty(uri)) {
            return false;
        }
        return uri.endsWith("login.html");
    }

    private boolean isHandlerMethod(Object handler) {
        return handler instanceof HandlerMethod;
    }

    private boolean validCookieToken(String token) {
        if (StringUtils.isEmpty(token)) {
            return false;
        }
        //根据token信息获取对应的用户信息
        LoginAccountInfo accountInfo = LoginAccountManager.validLoginAccountInfo(token);
        if (null == accountInfo) {
            return false;
        }
        SessionContextHolder.setToken(token);
        SessionContextHolder.setLoginAccountInfo(accountInfo);
        return true;
    }

    private boolean isParameterizableViewController(Object handler) {
        return handler instanceof ParameterizableViewController;
    }

    private boolean isResource(Object handler) {
        return handler instanceof ResourceHttpRequestHandler;
    }

    private String getCookieToken(HttpServletRequest request, String cookieKey) {
        //非public需要进行登陆校验
        String token = null;
        Cookie[] cookies = request.getCookies();
        if (cookies == null || cookies.length == 0) {
            return null;
        }
        for (Cookie cookie : cookies) {
            if (cookieKey.equalsIgnoreCase(cookie.getName())) {
                token = cookie.getValue();
                break;
            }
        }
        return token;
    }

    private void htmlWriteRedirect(HttpServletResponse response, String url) {
        try {
            response.sendRedirect(url);
        } catch (IOException e) {
            // ignore
        }
    }

    private boolean isAccessAuthorityPublic(HandlerMethod method) {
        if (method == null) {
            return false;
        }
        if (method.hasMethodAnnotation(AccessAuthority.class)) {
            AccessAuthority accessAuthority = method.getMethodAnnotation(AccessAuthority.class);
            if (accessAuthority == null) {
                return false;
            }
            String authority = accessAuthority.value();
            return AccessAuthority.PUBLIC.equalsIgnoreCase(authority);
        }
        return false;
    }

    private void ajaxWriteRedirect(HttpServletResponse response, String url) {
        try {
            Map<String, Object> map = new HashMap<>();
            map.put("redirectUrl", url);
            HttpBody<Object> httpBody = HttpBody.getInstance(SystemError.INVALID_LOGIN.getCode(), SystemError.INVALID_LOGIN.getMessage(), map);
            String message = JSONObject.toJSONString(httpBody);
            byte[] array = message.getBytes(Charset.forName("utf8"));
            response.getOutputStream().write(array);
            response.getOutputStream().flush();
        } catch (IOException e) {
            log.error("", e);
        }
    }
}
